Skip to content

Add AssignRouteAction to TrafficAction#898

Open
tbleher wants to merge 1 commit intoOpenSimulationInterface:masterfrom
tbleher:assignrouteaction
Open

Add AssignRouteAction to TrafficAction#898
tbleher wants to merge 1 commit intoOpenSimulationInterface:masterfrom
tbleher:assignrouteaction

Conversation

@tbleher
Copy link
Copy Markdown
Contributor

@tbleher tbleher commented Apr 9, 2026

This makes it possible to forward OpenSCENARIO XML's AssignRouteAction to a traffic participant.

Fixes #896.

This makes it possible to forward OpenSCENARIO XML's AssignRouteAction to
a traffic participant.

Fixes OpenSimulationInterface#896.

Signed-off-by: Thomas Bleher <thomas.tb.bleher@bmw.de>
@jdsika jdsika added the FeatureRequest Proposals which enhance the interface or add additional features. label Apr 15, 2026
Copy link
Copy Markdown
Contributor

@jdsika jdsika left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems fine for me, how is it with the deferred release of osi @pmai can we still merge new features?

@jdsika
Copy link
Copy Markdown
Contributor

jdsika commented Apr 23, 2026

PR #898: AssignRouteAction — Comprehensive Review & Analysis

Context: PR #898
adds AssignRouteAction to TrafficAction in osi_trafficcommand.proto, addressing
Issue #896.


1. Concepts: Trajectory, Path and Route

Three related but fundamentally different concepts exist in the OSI/OpenSCENARIO ecosystem
for controlling how a traffic participant moves through the road network. All three originate
from the OpenSCENARIO XML
RoutingAction
(a choice-type under PrivateAction), which groups all routing-related actions.

1.1 Comparison Table

Aspect Trajectory Path Route
OpenSCENARIO action FollowTrajectoryAction with TimeReference = Timing Same FollowTrajectoryAction with TimeReference = None AssignRouteAction
OpenSCENARIO data type TrajectoryShape (Polyline, Clothoid, ClothoidSpline, Nurbs) Same Trajectory (shape geometry only; timing ignored) RouteWaypoint list (≥2) with RouteStrategy per waypoint
OSI action FollowTrajectoryAction (osi_trafficcommand.proto, field 1) FollowPathAction (osi_trafficcommand.proto, field 2) AssignRouteAction (PR #898, field 13)
OSI data StatePoint list (position + timestamp + optional orientation) StatePoint list (position + optional orientation; timestamp ignored) RouteRouteSegmentLogicalLaneSegment (logical_lane_id + start_s + end_s) — defined in osi_route.proto
Coordinate system World coordinates (x, y, z) + absolute time World coordinates (x, y, z) Road-topological: logical lane IDs + s-positions along reference lines
FollowingMode position (force exact) or follow (best effort) position or follow N/A — inherently best-effort; no exact-following semantics
MotionControlAction ✅ Yes — directly overrides motion ✅ Yes — directly overrides motion No — "does not override any action that controls either lateral or longitudinal domain"
Participant freedom Minimal: prescribed position at prescribed time Medium: prescribed spatial path, free timing Maximum: road corridor only; participant controls lane choice, lateral position, speed, and timing
Analogy "Be at point X at time T" "Pass through these exact world-space points" "Take this highway, then exit at junction 5"

1.2 Key structural insight: OSC has no separate "FollowPathAction"

OpenSCENARIO uses a single FollowTrajectoryAction with a
TimeReference
choice (Timing vs None) to differentiate between:

  • Trajectory = geometry + timing (TimeReference = Timing)
  • Path = geometry only (TimeReference = None)

OSI splits this into two separate protobuf messages for clarity:

  • FollowTrajectoryAction — aligned with OSC FollowTrajectoryAction using a
    4/7D trajectory (position + time ± orientation) with Shape = Polyline
  • FollowPathAction — aligned with the same OSC action using a
    3/6D trajectory (position ± orientation, no time) with Shape = Polyline

Implication for documentation: The existing OSI comments on both actions cite
"OpenSCENARIO 1.0". This is correct — the action existed since 1.0 — but implementers
should be aware that in the OpenSCENARIO standard, the path/trajectory distinction is
a parameter choice within a single action, not two separate actions.

1.3 Key behavioral insight: AssignRouteAction does NOT control motion

Per
MotionControlAction,
actions that directly control an entity's motion (FollowTrajectoryAction, SpeedAction,
LaneChangeAction, LateralDistanceAction, LongitudinalDistanceAction, SynchronizeAction)
implement this interface. AssignRouteAction does not. Its OpenSCENARIO description
explicitly states: "This action does not override any action that controls either
lateral or longitudinal domain."

This means:

  • A route assignment coexists with SpeedAction, LaneChangeAction, etc.
  • The traffic participant uses the route as high-level guidance while obeying concurrent
    motion-control actions.
  • This is fundamentally different from FollowTrajectoryAction / FollowPathAction, which
    take over motion control.

2. OpenSCENARIO Route Model: Waypoints and RouteStrategy

2.1 Structure

An OpenSCENARIO Route consists of:

Route
├── closed: boolean (required) — whether the route loops back to start
├── name: string (required)
├── parameterDeclarations: ParameterDeclaration[] (optional)
└── waypoints: Waypoint[] (minimum 2)
    └── Waypoint
        ├── routeStrategy: RouteStrategy (required) — fastest|shortest|random|leastIntersections
        └── position: Position (required) — one of 10 position types

Each Waypoint
carries a Position
(choice of 10 types: WorldPosition, LanePosition, RoadPosition, RelativeObjectPosition,
RelativeWorldPosition, RelativeRoadPosition, RelativeLanePosition, RoutePosition,
GeoPosition, TrajectoryPosition) and a required
RouteStrategy enum.

2.2 RouteStrategy semantics: "from this waypoint to the next"

The spec describes RouteStrategy as "Strategy for path selection between waypoints
in a route."
The routeStrategy on waypoint N defines how to select the path FROM
waypoint N TO waypoint N+1
:

Waypoint A (routeStrategy=shortest) ──shortest──▶ Waypoint B (routeStrategy=fastest) ──fastest──▶ Waypoint C (routeStrategy=shortest)
                                                                                                      │
                                                                                                      ▼
                                                                                              (dead — no next WP,
                                                                                               unless closed=true)

Consequence: The routeStrategy on the last waypoint is semantically meaningless
(unless closed=true, where it defines the path from the last waypoint back to the first).
Yet the XSD schema makes routeStrategy required on every waypoint — an implementer
must provide a value even when it has no effect. This is an ambiguity in the standard.

2.3 Why 2 waypoints minimum?

A route by definition connects points in the road network. With 1 waypoint, there is no
segment and no route — that use case is served by
AcquirePositionAction
(single target position, implicit routing). The 2-waypoint minimum ensures at least one
segment with a meaningful RouteStrategy.

However, this raises a design question: does the first waypoint represent the entity's
current position, or an arbitrary anchor?

  • The standard does not require waypoint 1 to be the entity's current position.
  • If it IS the current position, it is redundant — the entity already knows where
    it is. Its routeStrategy is meaningful (defines path to waypoint 2), but its
    position is information the entity already possesses.
  • If it is NOT the current position, there is an implicit gap — how does the entity
    reach waypoint 1? The standard does not define this.
  • The design may be motivated by catalog reuse: pre-defined routes in catalogs have
    fixed starting positions independent of any entity.

2.4 Route (abstract) vs Concrete Route (resolved)

This distinction is central to the PR's resolving assumption:

Abstract Route (OpenSCENARIO) Concrete Route (OSI)
Definition A sequence of waypoints with strategies specifying HOW to find the path between them A fully resolved sequence of road segments specifying the EXACT path
Content Waypoints (positions of 10 types) + RouteStrategy per segment RouteSegmentLogicalLaneSegment (lane_id, start_s, end_s)
Ambiguity High: which roads? which lanes? which of several possible paths? None: every meter of road network is specified
RouteStrategy Present — drives path selection Absent — already consumed
Position types 10 types (world, lane, road, relative, geo, trajectory…) Logical lane s-positions only
Analogy "Drive Munich → Berlin via Nuremberg, use fastest roads" "A9 northbound lane 2 (s=0→165km), merge A73 lane 1 (s=0→45km), …"

Resolution is the process of converting abstract → concrete. It requires:

  1. Full road network graph (to evaluate strategies like "shortest" or "fastest")
  2. Position type resolution (converting all 10 position types to logical lane references)
  3. Graph search / pathfinding between consecutive waypoints
  4. Unrolling closed=true routes into linear segment sequences

3. The OSC → OSI Boundary: What Gets Resolved

The PR's \note states:

"…with the assumption that the scenario engine resolves the route (e.g. converting
a 'use shortest route' to a concrete route) before passing it to the traffic participant."

3.1 Resolution table

OpenSCENARIO element What the scenario engine does Present in OSI?
Waypoint.Position (10 types) Converts any position type to logical lane s-positions ❌ Absorbed into LogicalLaneSegment
Waypoint.RouteStrategy (fastest, shortest, random, leastIntersections) Executes pathfinding algorithm between consecutive waypoints ❌ Consumed entirely — not forwarded
Route.closed (boolean) Must unroll/repeat segments for loop routes ❌ OSI Route has no closed flag
Route.name, Route.parameterDeclarations Catalog/parameter handling at scenario level ❌ Not applicable at runtime
CatalogReference Catalog lookup and instantiation ❌ Resolved before transmission

3.2 Architectural consequences

  1. The scenario engine becomes the route planner. It must have full road-network
    knowledge to convert abstract waypoints + strategies into concrete LogicalLaneSegment
    sequences. This is a non-trivial requirement.

  2. Information loss is by design. The traffic participant never sees "shortest" vs
    "fastest" — only the resolved result. This is a deliberate interface simplification
    consistent with the principle that OSI transmits concrete state, not planning intent.

  3. Failure mode is silent. If the scenario engine cannot resolve a route (invalid
    waypoints, disconnected road network, ambiguous strategies), the AssignRouteAction
    simply cannot be transmitted. There is no error-propagation mechanism in OSI for this.

  4. Consistency with existing pattern. The existing
    AcquireGlobalPositionAction (field 3) already describes itself as assigning
    "the shortest route" — so the scenario engine was always implicitly doing some
    routing. This PR makes the resolved route explicit and richer.


4. OSI RoutingAction Landscape After PR #898

After merging, OSI will have three routing-related actions in TrafficAction. Understanding
how they relate prevents confusion and misapplication:

OSI Action OSC Equivalent Input Resolution required Participant freedom Use case
AcquireGlobalPositionAction (field 3) AcquirePositionAction Single world position (x,y,z) Implicit — participant finds own route Full — only destination matters "Go to this location"
FollowPathAction (field 2) FollowTrajectoryAction (TimeRef=None) World-space waypoints (StatePoint[]) None — path is fully specified Low — must follow points (FollowingMode) "Pass through these exact world-space points"
AssignRouteAction (field 13, PR #898) AssignRouteAction Road-topological route (LogicalLaneSegment[]) Pre-resolved by scenario engine High — corridor only "Take this road-network path"

Critical distinction: AcquireGlobalPositionAction vs AssignRouteAction

Both involve "assigning a route," but at fundamentally different levels:

  • AcquireGlobalPositionAction: Provides only a destination. The traffic participant
    performs its own routing. The existing documentation says it assigns "the shortest route"
    — but this is the participant's internal decision, not an explicit route.
  • AssignRouteAction: Provides the complete, resolved route as a sequence of logical
    lane segments. The scenario engine has already made all routing decisions.

⚠️ Risk of confusion: The AcquireGlobalPositionAction documentation uses the word
"route" in a colloquial sense ("assigns a route"), which now conflicts with the precise
meaning of AssignRouteAction. This should be clarified.


5. Review of PR #898: Issues and Recommendations

5.1 What the PR adds (diff summary)

// In osi_trafficcommand.proto:
import "osi_route.proto";                            // new import

// In message TrafficAction:
optional AssignRouteAction assign_route_action = 13; // new field

// New nested message:
message AssignRouteAction
{
    optional ActionHeader action_header = 1;
    optional Route route = 2;
}

5.2 Positive aspects

# Finding
1 Clean reuse: Correctly reuses the existing Route message from osi_route.proto (already used in HostVehicleData.route, field 14 in osi_hostvehicledata.proto). No redundant data structures.
2 Consistent pattern: Follows the ActionHeader + payload structure of all 12 existing actions.
3 Correct field numbering: Field 13 is sequential after teleport_action = 12, no conflicts.
4 Addresses a real gap: Issue #896 correctly identifies that AcquireGlobalPositionAction (single position + implicit routing) cannot losslessly represent an explicit multi-waypoint route.

5.3 Issues and improvement recommendations

Each item below includes the current text, the problem, and a recommended fix
aimed at eliminating ambiguity and preventing incorrect implementation.


Issue 1: Grammar error

Current text:

// A AssignRouteAction

Problem: English grammar requires "An" before vowel sounds.

Recommended fix:

// An AssignRouteAction

Issue 2: Version reference inconsistency

Current text:

// \note This action is aligned with the AssignRouteAction of OpenSCENARIO
// 1.3, with the assumption that …

Problem: All 12 existing OSI actions reference "OpenSCENARIO 1.0" (e.g.,
FollowTrajectoryAction, SpeedAction, LaneChangeAction). AssignRouteAction
has existed in OpenSCENARIO since version 1.0. Citing "1.3" without explanation
implies either a deliberate deviation or an error, and may lead implementers to
believe the mapping only applies to 1.3+.

Recommended fix — option A (align):

// \note This action is aligned with the AssignRouteAction of OpenSCENARIO
// 1.0, with the assumption that …

Recommended fix — option B (explain):

// \note This action is aligned with the AssignRouteAction of OpenSCENARIO
// (introduced in 1.0; mapping verified against 1.3 semantics), with the
// assumption that …

Issue 3: The "resolving" assumption is under-specified

Current text:

// \note This action is aligned with the AssignRouteAction of OpenSCENARIO
// 1.3, with the assumption that the scenario engine resolves the route
// (e.g. converting a "use shortest route" to a concrete route) before
// passing it to the traffic participant.

Problem: The note correctly states that resolution is required but does not
specify what must be resolved. An implementer may not realize that:

  • All 10 OpenSCENARIO Position types must be converted to logical lane s-positions
  • RouteStrategy (fastest, shortest, random, leastIntersections) is consumed and not forwarded
  • Route.closed must be unrolled into a linear segment sequence (OSI Route has no closed field)
  • Catalog references must be fully resolved

Recommended fix:

// \note This action is aligned with the AssignRouteAction of OpenSCENARIO
// 1.0, with the assumption that the scenario engine fully resolves the
// route before passing it to the traffic participant. Resolution includes:
// - Converting all OpenSCENARIO Position types to logical lane s-positions
// - Applying the RouteStrategy (e.g., shortest, fastest) to determine the
//   concrete path between waypoints
// - Unrolling closed routes into a linear segment sequence
// - Resolving any CatalogReferences
// The resulting OSI Route contains only concrete LogicalLaneSegments;
// the original RouteStrategy values and Position types are not forwarded.

Issue 4: Missing non-MotionControlAction clarification

Problem: OpenSCENARIO explicitly states that AssignRouteAction "does not
override any action that controls either lateral or longitudinal domain."

(MotionControlAction). This is a critical behavioral difference from
FollowTrajectoryAction and FollowPathAction, which DO override motion. Without
this note, implementers may incorrectly treat the route as a motion-override command.

Recommended addition:

// \note Unlike FollowTrajectoryAction and FollowPathAction, this action
// does not override lateral or longitudinal motion control. It provides
// routing guidance that coexists with other motion-controlling actions
// (e.g. SpeedAction, LaneChangeAction).

Issue 5: AcquireGlobalPositionAction documentation now ambiguous

Problem: The existing AcquireGlobalPositionAction comment reads:

"This action assigns a route to a traffic participant."

With the addition of an explicit AssignRouteAction, this wording becomes confusing.
Both actions "assign a route" but in fundamentally different ways:

AcquireGlobalPositionAction AssignRouteAction
Provides Single destination position Complete resolved route
Routing done by Traffic participant Scenario engine
Route specification Implicit ("shortest") Explicit (LogicalLaneSegments)

Recommended clarification (in existing AcquireGlobalPositionAction comment):

// \brief Acquire global position action.
//
// This action assigns a target position to a traffic participant.
// The traffic participant is expected to navigate to this position
// autonomously (e.g. via the shortest route). Unlike AssignRouteAction,
// no explicit route is provided — routing is left to the participant.

Issue 6: Route.route_id must be set

Problem: The OSI Route message's route_id field has \rules is_set \endrules,
making it mandatory. When the scenario engine constructs a resolved route for
AssignRouteAction, it must assign a unique route_id. This is not mentioned in
the PR and could be overlooked by implementers.

Recommended addition (in AssignRouteAction or its \note):

// \note The Route.route_id must be set and unique within all route
// messages exchanged with this traffic participant.

6. Summary: Making Comments Precise and Avoiding Misapplication

The core challenge is that the PR's current \note is directionally correct but
insufficiently precise
. An implementer reading only the OSI proto comments (without
studying the OpenSCENARIO standard) could:

Misinterpretation risk Cause Fix
Treat AssignRouteAction as a motion-override command (like FollowPathAction) No mention that it's NOT a MotionControlAction Add explicit non-override note (Issue 4)
Forward RouteStrategy values or expect them in OSI "Resolving" assumption doesn't list what is consumed Enumerate resolved elements (Issue 3)
Confuse AcquireGlobalPositionAction with AssignRouteAction Both say "assigns a route" Clarify the distinction (Issue 5)
Reference OSC 1.3 only, missing 1.0 compatibility Version citation says "1.3" Align or explain version (Issue 2)
Forget to set Route.route_id Not mentioned in the action Note the mandatory field (Issue 6)
Pass closed=true routes without unrolling closed not mentioned Document closed-route handling (Issue 3)

Guiding principles for precise OSI documentation

  1. Name what is consumed, not just that resolution happens. The note should enumerate
    specific OpenSCENARIO elements that do NOT appear in OSI.
  2. State behavioral category explicitly. "This action does / does not control motion"
    is the single most important sentence for implementers.
  3. Distinguish similar actions. When multiple actions occupy related semantic space
    (AcquireGlobalPosition vs AssignRoute), cross-reference and contrast them.
  4. Cite the standard version precisely. Either align with existing "1.0" citations
    or explain why a different version is referenced.
  5. Reference mandatory field constraints. When the action's payload has mandatory
    fields (like route_id), mention them to prevent validation failures at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FeatureRequest Proposals which enhance the interface or add additional features.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support AssignRouteAction in OSI

2 participants